忍者刘米米

17.Optional Chaining

Optional Chaining

Multiple queries can be chained together, and the entire chain fails gracefully if any link in the chain is nil.

Optional Chaining as an Alternative to Forced Unwrapping

A property that normally returns an Int will return an Int? when accessed through optional chaining.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Person {
var residence: Residence?
}

class Residence {
var numberOfRooms = 1
}

let john = Person()

let roomCount = john.residence!.numberOfRooms
// this triggers a runtime error

if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// Prints "Unable to retrieve the number of rooms."

john.residence = Residence()

if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// Prints "John's residence has 1 room(s)."

Defining Model Classes for Optional Chaining

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Person {
var residence: Residence?
}

class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
get {
return rooms[i]
}
set {
rooms[i] = newValue
}
}
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
var address: Address?
}

class Room {
let name: String
init(name: String) { self.name = name }
}

class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if buildingNumber != nil && street != nil {
return "\(buildingNumber) \(street)"
} else if buildingName != nil {
return buildingName
} else {
return nil
}
}
}

Accessing Properties Through Optional Chaining

the attempt to set the address property of john.residence will fail, because john.residence is currently nil.

1
2
3
4
5
6
7
8
9
10
11
12
13
let john = Person()
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}

// Prints "Unable to retrieve the number of rooms."

let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress

The function prints “Function was called” before returning a value, which lets you see whether the right hand side of the = operator was evaluated.

1
2
3
4
5
6
7
8
9
10
11
func createAddress() -> Address {
print("Function was called.")

let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"

return someAddress
}

john.residence?.address = createAddress()

Calling Methods Through Optional Chaining

functions and methods with no return type have an implicit return type of Void

1
2
3
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
  1. If you call this method on an optional value with optional chaining, the method’s return type will be Void?, not Void, because return values are always of an optional type when called through optional chaining
  2. Compare the return value from the printNumberOfRooms call against nil to see if the method call was successful
1
2
3
4
5
6
if john.residence?.printNumberOfRooms() != nil {
print("It was possible to print the number of rooms.")
} else {
print("It was not possible to print the number of rooms.")
}
// Prints "It was not possible to print the number of rooms."

Any attempt to set a property through optional chaining returns a value of type Void?, which enables you to compare against nil to see if the property was set successfully

1
2
3
4
5
6
if (john.residence?.address = someAddress) != nil {
print("It was possible to set the address.")
} else {
print("It was not possible to set the address.")
}
// Prints "It was not possible to set the address."

Accessing Subscripts Through Optional Chaining

The optional chaining question mark always follows immediately after the part of the expression that is optional

1
2
3
4
5
6
if let firstRoomName = john.residence?[0].name {
print("The first room name is \(firstRoomName).")
} else {
print("Unable to retrieve the first room name.")
}
// Prints "Unable to retrieve the first room name."

Similarly, you can try to set a new value through a subscript with optional chaining:

1
2
3
john.residence?[0] = Room(name: "Bathroom")

//This subscript setting attempt also fails, because residence is currently nil.

Now , it is ok

1
2
3
4
5
6
7
8
9
10
11
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "Living Room"))
johnsHouse.rooms.append(Room(name: "Kitchen"))
john.residence = johnsHouse

if let firstRoomName = john.residence?[0].name {
print("The first room name is \(firstRoomName).")
} else {
print("Unable to retrieve the first room name.")
}
// Prints "The first room name is Living Room."

Accessing Subscripts of Optional Type

1
2
3
4
5
6
7
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0] += 1
testScores["Brian"]?[0] = 72
// the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]

// The third call fails, because the testScores dictionary does not contain a key for "Brian".

Linking Multiple Levels of Chaining

  • If you try to retrieve an Int value through optional chaining, an Int? is always returned, no matter how many levels of chaining are used.
  • Similarly, if you try to retrieve an Int? value through optional chaining, an Int? is always returned, no matter how many levels of chaining are used.
1
2
3
4
5
6
7
if let johnsStreet = john.residence?.address?.street {
print("John's street name is \(johnsStreet).")
} else {
print("Unable to retrieve the address.")
}
// Prints "Unable to retrieve the address."
// the value of john.residence.address is currently nil.so it fails

Now, it is ok

1
2
3
4
5
6
7
8
9
10
11
let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence?.address = johnsAddress

if let johnsStreet = john.residence?.address?.street {
print("John's street name is \(johnsStreet).")
} else {
print("Unable to retrieve the address.")
}
// Prints "John's street name is Laurel Street."

Chaining on Methods with Optional Return Values

1
2
3
4
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
print("John's building identifier is \(buildingIdentifier).")
}
// Prints "John's building identifier is The Larches."

the optional value you are chaining on is the buildingIdentifier() method’s return value, and not the buildingIdentifier() method itself.

1
2
3
4
5
6
7
8
9
if let beginsWithThe =
john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
if beginsWithThe {
print("John's building identifier begins with \"The\".")
} else {
print("John's building identifier does not begin with \"The\".")
}
}
// Prints "John's building identifier begins with "The"."